// MobileFX.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "MobileFX.h"

#include <msxml.h>
#include <objsafe.h>

#include <initguid.h>
#include <imaging.h>

#define ARRAY_SIZE(x) ((sizeof(x)) / (sizeof(x[0])))

#define IDC_FIRST_SOUND 1000

// A structure to store details relateed to a
// sound effect button
typedef struct {
	WCHAR szName[MAX_PATH];		// name (not used by current user interface)
	WCHAR szSound[MAX_PATH];	// path to sound effect wave file
	HBITMAP hbmpIcon;			// bitmap to display on button
} BUTTON;

// Given a path relative to the location of the application executable
// convert it into a full path usable by system APIs
static void GetFullPathToFile(WCHAR * pszFullPath, WCHAR * pszFilename)
{
	GetModuleFileName(GetModuleHandle(NULL), pszFullPath, MAX_PATH);
	wcscpy(wcsrchr(pszFullPath, '\\') + 1, pszFilename);
}

// Draw an alpha blended image on the device context 'hdc' within
// the rectangle defined by 'prcBounds'.
static BOOL DrawAlphaImage(HDC hDC, RECT * prcBounds, WCHAR * pszImageFileName)
{
	// Create an instance of the ImagingFactory class
	IImagingFactory *pFactory = NULL;
	HRESULT hr = CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&pFactory);
	if (hr == S_OK)
	{
		// Call the CreateImageFromFile method to load the image
		// into memory.
		IImage *pImage = NULL;
		hr = pFactory->CreateImageFromFile(pszImageFileName, &pImage);
		if (hr == S_OK)
		{
			// And finally draw the image into the device context
			hr = pImage->Draw(hDC, prcBounds, NULL);

			pImage->Release();
			pImage = NULL;
		}

		pFactory->Release();
		pFactory = NULL;
	}

	return (hr == S_OK);
}

static HBITMAP CreateTile(WCHAR * pszIconFileName)
{
	RECT rcBounds = {0, 0, 48, 48};

	HDC hdc = GetDC(NULL);
	HDC memDC = CreateCompatibleDC(hdc);
	HBITMAP hbmp = CreateCompatibleBitmap(hdc, rcBounds.right - rcBounds.left, rcBounds.bottom - rcBounds.top);
	ReleaseDC(NULL, hdc);

	SelectObject(memDC, hbmp);

	if (!DrawAlphaImage(memDC, &rcBounds, pszIconFileName))
	{
		// [CF] - TODO - remove hbmp from DC...
		DeleteObject(hbmp);
		hbmp = NULL;
	}

	DeleteDC(memDC);

	return hbmp;
}

static BOOL ParseButtonInfo(WCHAR * pszSoundPackName, IXMLDOMNode *pButtonElement, BUTTON * pButtonInfo)
{
	IXMLDOMNodeList *pChildNodes = NULL;
	long len = 0;

	// Clear out the structure used to store
	// button info
	memset(pButtonInfo, 0, sizeof(BUTTON));

	// Get the list of child nodes
	if (pButtonElement->get_childNodes(&pChildNodes) == S_OK
		&& pChildNodes->get_length(&len) == S_OK)
	{
		// Loop over each of the child nodes
		for (long i = 0; i < len; i++)
		{
			// Get the next child
			IXMLDOMNode *pChildNode = NULL;
			if (pChildNodes->get_item(i, &pChildNode) == S_OK)
			{
				BSTR name = NULL;
				BSTR value = NULL;

				// Fetch it's node name and textual value
				if (pChildNode->get_nodeName(&name) == S_OK
					&& pChildNode->get_text(&value) == S_OK)
				{
					// Then perform different processing based upon the
					// name of the node
					if (wcscmp(name, L"Name") == 0)
					{
						wcscpy(pButtonInfo->szName, value);
					}
					else if (wcscmp(name, L"SoundFile") == 0)
					{
						WCHAR szRelativePath[MAX_PATH];

						swprintf(szRelativePath, L"effects\\%s\\%s", pszSoundPackName, value);
						GetFullPathToFile(pButtonInfo->szSound, szRelativePath);
					}
					else if (wcscmp(name, L"IconFile") == 0)
					{
						WCHAR szRelativePath[MAX_PATH];
						WCHAR szFullPath[MAX_PATH];

						swprintf(szRelativePath, L"images\\%s\\%s", pszSoundPackName, value);
						GetFullPathToFile(szFullPath, szRelativePath);

						pButtonInfo->hbmpIcon = CreateTile(szFullPath);
					}
				}
	
				// Free up the nodename and text value
				// strings
				if (name)
				{
					SysFreeString(name);
					name = NULL;
				}
	
				if (value)
				{
					SysFreeString(value);
					value = NULL;
				}

				pChildNode->Release();
				pChildNode = NULL;
			}
		}
	}

	if (pChildNodes)
	{
		pChildNodes->Release();
		pChildNodes = NULL;
	}
	
	// Only return success if we have managed to load the button icon
	// and the sound effect file exists
	return (pButtonInfo->hbmpIcon != NULL) && (GetFileAttributes(pButtonInfo->szSound) != 0xFFFFFFFF);
}


static int ParseSoundPackFile(WCHAR * pszSoundPackName, BUTTON * pButtons, int maxButtonCount)
{
	int buttonCount = 0;

	WCHAR szFilename[MAX_PATH];
	GetFullPathToFile(szFilename, pszSoundPackName);
	wcscat(szFilename, L".xml");

	// Create an instance of the XML Document class
	IXMLDOMDocument *pDoc = NULL;
	HRESULT hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (void**)&pDoc);
	if (hr != S_OK)
		return 0;

	// Load the XML file
	VARIANT_BOOL bSuccess = VARIANT_FALSE;
	VARIANT filename;

	VariantInit(&filename);
	V_BSTR(&filename) = SysAllocString(szFilename);
	V_VT(&filename) = VT_BSTR;

	hr = pDoc->load(filename, &bSuccess);
	if (hr == S_OK && bSuccess == VARIANT_TRUE)
	{
		// Execute an XPATH query to find all the Button elements
		IXMLDOMNodeList *pButtonNodes = NULL;
		LONG len = 0;
		BSTR xpath = SysAllocString(L"/MobileFX/SoundPack/Buttons");

		if (pDoc->selectNodes(xpath, &pButtonNodes) == S_OK
			&& pButtonNodes->get_length(&len) == S_OK
			&& len <= maxButtonCount)
		{
			// Then iterate over each of the returned nodes
			for (LONG i = 0; i < len; i++)
			{
				// Fetch the next button
				IXMLDOMNode *pButtonNode = NULL;
				if (pButtonNodes->get_item(i, &pButtonNode) == S_OK)
				{
					// Parse the button's details
					if (ParseButtonInfo(pszSoundPackName, pButtonNode, &pButtons[buttonCount]))
					{
						buttonCount++;
					}

					// Free the node
					pButtonNode->Release();
					pButtonNode = NULL;
				}
			}
		}

		// Free the resources
		if (pButtonNodes)
		{
			pButtonNodes->Release();
			pButtonNodes = NULL;
		}

		SysFreeString(xpath);
	}

	pDoc->Release();
	pDoc = NULL;

	return buttonCount;
}

static void PlaySoundEffect(WCHAR * pszSoundEffectFile)
{
	// Start to play the requested sound effect
	PlaySound(pszSoundEffectFile, NULL, SND_ASYNC | SND_FILENAME | SND_NODEFAULT);
}

static void StopSoundEffects()
{
	// Stop any sound which is currently still playing
	PlaySound(NULL, NULL, SND_FILENAME);
}

static INT_PTR CALLBACK MobileFXDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	static BUTTON buttons[16];
	static int buttonCount = -1;
	static HBITMAP hbmpBlankButton = NULL;

    switch (message)
    {
        case WM_INITDIALOG:
            {
                // Create a Done button and size it.  
                SHINITDLGINFO shidi;
                shidi.dwMask = SHIDIM_FLAGS;
                shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;
                shidi.hDlg = hDlg;
                SHInitDialog(&shidi);

				// Load a bitmap used for buttons which don't
				// have sound effects associated with them
				hbmpBlankButton = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BITMAP_BLANKBUTTON));
				
				// Parse the XML based soundpack file
				buttonCount = ParseSoundPackFile(L"default", buttons, ARRAY_SIZE(buttons));
				if (buttonCount == 0)
				{
					// An error occurred so display an error message
					// and exit the application
					MessageBox(hDlg, L"Could not parse soundpack file", L"Error", MB_ICONEXCLAMATION | MB_OK);
					EndDialog(hDlg, IDCANCEL);
				}
				else
				{
					// Create a image based "button" for each of the sound effects
					int left = 9;
					int top = 9;

					for (int i = 0; i < 16; i++)
					{
						HWND hWndPicture = CreateWindow(L"static",
							NULL,
							WS_CHILD | WS_VISIBLE | SS_BITMAP | SS_NOTIFY, 
							left + (i % 4 * 58),
							top + (i / 4 * 58),
							48,
							48,
							hDlg,
							NULL,
							GetModuleHandle(NULL),
							NULL);

						if (i < buttonCount)
						{
							// This button has a sound effect associated with it
							SetWindowLong(hWndPicture, GWL_ID, IDC_FIRST_SOUND + i);
							SendMessage(hWndPicture, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)buttons[i].hbmpIcon);
						}
						else
						{
							// This button doesn't have a sound effect associated with it
							SetWindowLong(hWndPicture, GWL_ID, IDC_STATIC);
							SendMessage(hWndPicture, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbmpBlankButton);
						}
					}
				}
            }
            return (INT_PTR)TRUE;

		case WM_CTLCOLORDLG:
			// Colour the background black
			return (INT_PTR)GetStockObject(BLACK_BRUSH);

        case WM_COMMAND:
			// Check if the command ID is within the range we
			// assigned to our sound "buttons"
			if (LOWORD(wParam) >= IDC_FIRST_SOUND && LOWORD(wParam) <= (IDC_FIRST_SOUND + buttonCount))
			{
				// If it was then play the associated sound effect
				if (HIWORD(wParam) == STN_CLICKED)
				{
					PlaySoundEffect(buttons[LOWORD(wParam) - IDC_FIRST_SOUND].szSound);
				}
			}
			else if (LOWORD(wParam) == IDOK)
			{
				EndDialog(hDlg, LOWORD(wParam));
				return (INT_PTR)TRUE;
            }
            break;

        case WM_CLOSE:
            EndDialog(hDlg, message);
            return (INT_PTR)TRUE;

		case WM_DESTROY:
			StopSoundEffects();

			if (hbmpBlankButton)
			{
				DeleteObject(hbmpBlankButton);
				hbmpBlankButton = NULL;
			}
			break;

    }
    return (INT_PTR)FALSE;
}

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
	CoInitializeEx(NULL, COINIT_MULTITHREADED);
		
	return DialogBox(hInstance, (LPCTSTR)IDD_MOBILEFX, NULL, MobileFXDialog);
}